package com.suemi.missile.common.mql.maker;

import com.suemi.missile.common.mql.domain.Condition;
import com.suemi.missile.common.mql.domain.Where;
import com.suemi.missile.common.mql.exception.MqlParseException;
import org.elasticsearch.common.lucene.search.function.CombineFunction;
import org.elasticsearch.common.lucene.search.function.FiltersFunctionScoreQuery;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.ConstantScoreQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder.FilterFunctionBuilder;
import org.elasticsearch.index.query.functionscore.WeightBuilder;

import java.util.ArrayList;
import java.util.List;

public class WhereVisitor {

  public static QueryBuilder visitWhere(Where where) throws MqlParseException {

    if(where instanceof Condition) return visitCondition((Condition)where);

    BoolQueryBuilder inner = new BoolQueryBuilder();
    List<FilterFunctionBuilder> filters = new ArrayList<>();

    for(Where subWhere : where.getWheres()) {
      if(subWhere instanceof Condition) {
        Condition cond = (Condition) subWhere;
        ConstantScoreQueryBuilder subQuery = (ConstantScoreQueryBuilder) visitCondition(cond);
        if(where.getConn() == Where.CONN.AND) inner.must(subQuery.innerQuery());
        else if(where.getConn() == Where.CONN.OR) inner.should(subQuery.innerQuery());
        FilterFunctionBuilder filter = new FilterFunctionBuilder(subQuery.innerQuery(),new WeightBuilder().setWeight(cond.getBoost()==null?1.0f:cond.getBoost()));
        filters.add(filter);
      } else {
        FunctionScoreQueryBuilder subQuery = (FunctionScoreQueryBuilder) visitWhere(subWhere);
        if(where.getConn() == Where.CONN.AND) inner.must(subQuery.query());
        else if(where.getConn() == Where.CONN.OR) inner.should(subQuery.query());
        FilterFunctionBuilder filter = new FilterFunctionBuilder(subQuery.query(),new WeightBuilder().setWeight(subWhere.getBoost()==null?1.0f:subWhere.getBoost()));
        filters.add(filter);
      }
    }

    FilterFunctionBuilder[] filterBuilders = filters.toArray(new FilterFunctionBuilder[filters.size()]);

    FunctionScoreQueryBuilder query = new FunctionScoreQueryBuilder(inner,filterBuilders);
    query.boostMode(CombineFunction.MAX);
    query.scoreMode(FiltersFunctionScoreQuery.ScoreMode.MAX);
    return query;
  }

  public static QueryBuilder visitCondition(Condition cond) throws MqlParseException {
    QueryBuilder subQuery;
    String queryStr;
    switch (cond.getOpear()) {
      case CONTAINS:
        queryStr = (String) cond.getValue();
        subQuery = cond.getName().toUpperCase().equals("ANY") ?
                QueryBuilders.matchQuery("_all", queryStr) : QueryBuilders.matchQuery(cond.getName(), queryStr);
        break;
      default:
        throw new MqlParseException("not define type " + cond.getName());
    }

    float score = cond.getBoost()==null? 1.0f : cond.getBoost();
    return new ConstantScoreQueryBuilder(subQuery).boost(score);
  }
}
